package listener_test

import (
	"context"
	"errors"

	"github.com/golang/mock/gomock"
	"istio.io/istio/pkg/kube/krt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8scert "k8s.io/client-go/util/cert"
	"k8s.io/utils/ptr"
	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
	gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/kgateway-dev/kgateway/v2/api/annotations"
	"github.com/kgateway-dev/kgateway/v2/pkg/kgateway/query"
	"github.com/kgateway-dev/kgateway/v2/pkg/kgateway/query/mocks"
	"github.com/kgateway-dev/kgateway/v2/pkg/kgateway/translator/listener"
	"github.com/kgateway-dev/kgateway/v2/pkg/kgateway/wellknown"
	"github.com/kgateway-dev/kgateway/v2/pkg/pluginsdk/ir"
	"github.com/kgateway-dev/kgateway/v2/pkg/pluginsdk/reporter"
	"github.com/kgateway-dev/kgateway/v2/pkg/reports"
)

var (
	ctx              context.Context
	gwListener       gwv1.Listener
	gateway          *gwv1.Gateway
	listenerReporter reporter.ListenerReporter
	statusReporter   reporter.Reporter
	ml               *listener.MergedListeners
	ctrl             *gomock.Controller
	queries          *mocks.MockGatewayQueries
)

func lisToIr(l gwv1.Listener) ir.Listener {
	return ir.Listener{
		Listener: l,
		Parent: &gwv1.Gateway{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "gw",
				Namespace: "default",
			},
		},
	}
}

func tcpToIr(tcpRoute *gwv1a2.TCPRoute) *ir.TcpRouteIR {
	routeir := &ir.TcpRouteIR{
		ObjectSource: ir.ObjectSource{
			Namespace: tcpRoute.Namespace,
			Name:      tcpRoute.Name,
			Kind:      "TCPRoute",
			Group:     gwv1.GroupVersion.Group,
		},
		SourceObject: tcpRoute,
		ParentRefs:   tcpRoute.Spec.ParentRefs,
	}
	if len(tcpRoute.Spec.Rules) == 0 {
		return routeir
	}
	for _, b := range tcpRoute.Spec.Rules[0].BackendRefs {
		routeir.Backends = append(routeir.Backends, ir.BackendRefIR{
			ClusterName:   string(b.Name),
			BackendObject: &ir.BackendObjectIR{},
			Weight:        uint32(ptr.Deref(b.Weight, 1)), //nolint:gosec // G115: Gateway API backend weight is int32, always non-negative, safe to convert to uint32
		})
	}

	return routeir
}

func tlsToIr(tlsRoute *gwv1a2.TLSRoute) *ir.TlsRouteIR {
	routeir := &ir.TlsRouteIR{
		ObjectSource: ir.ObjectSource{
			Namespace: tlsRoute.Namespace,
			Name:      tlsRoute.Name,
			Kind:      "TLSRoute",
			Group:     gwv1.GroupVersion.Group,
		},
		SourceObject: tlsRoute,
		ParentRefs:   tlsRoute.Spec.ParentRefs,
	}
	if len(tlsRoute.Spec.Rules) == 0 {
		return routeir
	}
	for _, b := range tlsRoute.Spec.Rules[0].BackendRefs {
		routeir.Backends = append(routeir.Backends, ir.BackendRefIR{
			ClusterName:   string(b.Name),
			BackendObject: &ir.BackendObjectIR{},
			Weight:        uint32(ptr.Deref(b.Weight, 1)), //nolint:gosec // G115: Gateway API backend weight is int32, always non-negative, safe to convert to uint32
		})
	}
	return routeir
}

var _ = Describe("Translator TCPRoute Listener", func() {
	Context("TCP", func() {
		BeforeEach(func() {
			ctx = context.Background()
			ctrl = gomock.NewController(GinkgoT())
			queries = mocks.NewMockGatewayQueries(ctrl)

			gwListener = gwv1.Listener{
				Name:     "foo-tcp",
				Protocol: gwv1.TCPProtocolType,
				Port:     8080,
			}

			gateway = &gwv1.Gateway{
				ObjectMeta: metav1.ObjectMeta{Name: "test-gateway", Namespace: "default"},
			}

			rm := reports.NewReportMap()
			statusReporter = reports.NewReporter(&rm)
			gatewayReporter := statusReporter.Gateway(gateway)
			listenerReporter = gatewayReporter.Listener(&gwListener)
			ml = &listener.MergedListeners{
				Listeners: []*listener.MergedListener{},
				Queries:   queries,
			}

		})

		AfterEach(func() {
			ctrl.Finish()
		})

		Describe("translates gateway API resources to kgateway proxy listeners", func() {
			It("should create a TCP listener with multiple backend references", func() {
				By("Creating a TCPRoute with multiple backend references")
				tcpRoute := tcpRoute("test-tcp-route")
				tcpRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TCPRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
									Weight: ptr.To(int32(50)),
								},
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc2",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8082)),
									},
									Weight: ptr.To(int32(50)),
								},
							},
						},
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tcpToIr(tcpRoute),
					},
				}

				By("Appending the TCP listener")
				ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that the TCP listener is properly created with multiple backend references")
				Expect(ml.Listeners).To(HaveLen(1))
				Expect(ml.Listeners[0].TcpFilterChains).To(HaveLen(1))

				// Translate the listener to get the actual kgateway listener
				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tcpListener := translatedListener.TcpFilterChain[0]
				Expect(tcpListener.BackendRefs).To(HaveLen(2))
			})
			It("should handle TCPRoute with empty backend references", func() {
				By("Creating a TCPRoute with an empty backend references")
				tcpRoute := tcpRoute("test-empty-backend")
				tcpRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TCPRouteRule{
						{BackendRefs: []gwv1.BackendRef{}}, // Empty BackendRefs
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tcpToIr(tcpRoute),
					},
				}

				By("Appending the TCP listener")
				ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that a TCP listener is created with no TCPHosts")
				Expect(ml.Listeners).To(HaveLen(1))

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(BeEmpty(), "Expected no TCP listeners due to empty backend references")
			})

			It("should not append a listener for an unsupported protocol", func() {
				By("Creating a Listener with an unsupported protocol")
				badListener := gwv1.Listener{
					Name:     "foo-unsupported",
					Protocol: gwv1.ProtocolType("UNSUPPORTED"),
					Port:     8080,
				}

				By("Appending the TCP listener generates an error")
				err := ml.AppendListener(lisToIr(badListener), nil, listenerReporter)
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("unsupported protocol"))

				By("Validating that TCP listeners is empty due to the unsupported protocol")
				Expect(ml.Listeners).To(BeEmpty(), "Expected no listeners due to unsupported protocol")
			})

			It("should skip routes with invalid parent references and process valid ones", func() {
				By("Creating a TCPRoute with a backend reference")
				validRoute := tcpRoute("valid-tcp-route")
				validRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TCPRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
								},
							},
						},
					},
				}

				By("Creating an invalid TCPRoute with no parent references")
				invalidRoute := tcpRoute("invalid-tcp-route")
				invalidRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{}, // No parent reference provided
					},
				}

				By("Creating the RouteInfo with valid and invalid TCPRoutes")
				routes := []*query.RouteInfo{
					{
						Object: tcpToIr(validRoute),
					},
					{
						Object: tcpToIr(invalidRoute),
					},
				}

				By("Appending the TCP listener")
				ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that one single destination TCP listener is created")
				Expect(ml.Listeners).To(HaveLen(1)) // One valid listener

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tcpListener := translatedListener.TcpFilterChain[0]
				Expect(tcpListener).NotTo(BeNil())
				Expect(tcpListener.BackendRefs[0]).NotTo(BeNil())
				Expect(tcpListener.BackendRefs[0].ClusterName).To(Equal("backend-svc1"))
			})

			It("should create a TCP listener with a single weighted backend reference", func() {
				By("Creating a weighted TCPRoute with a single backend reference")
				tcpRoute := tcpRoute("test-tcp-route")
				tcpRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TCPRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
									Weight: ptr.To(int32(100)),
								},
							},
						},
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tcpToIr(tcpRoute),
					},
				}

				By("Appending the TCP listener")
				ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that one TCP listener is created with a single destination")
				Expect(ml.Listeners).To(HaveLen(1))

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tcpListener := translatedListener.TcpFilterChain[0]
				Expect(tcpListener).NotTo(BeNil())

				// Access the destination field properly
				Expect(tcpListener.BackendRefs).To(HaveLen(1))
				singleDestination := tcpListener.BackendRefs[0]
				Expect(singleDestination).NotTo(BeNil(), "Expected a single-destination")
				Expect(tcpListener.BackendRefs[0].ClusterName).To(Equal("backend-svc1"))
			})

			It("should create a TCP listener with multiple weighted backend references", func() {
				By("Creating a TCPRoute with multiple weighted backend references")
				tcpRoute := tcpRoute("test-multi-weighted-tcp-route")
				tcpRoute.Spec = gwv1a2.TCPRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TCPRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
									Weight: ptr.To(int32(60)),
								},
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc2",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8082)),
									},
									Weight: ptr.To(int32(40)),
								},
							},
						},
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tcpToIr(tcpRoute),
					},
				}

				By("Appending the TCP listener")
				ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that one TCP listener is created with multiple weighted destinations")
				Expect(ml.Listeners).To(HaveLen(1))

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tcpListener := translatedListener.TcpFilterChain[0]
				Expect(tcpListener).NotTo(BeNil())

				// Access the multi-destination field
				multiDestination := tcpListener.BackendRefs
				Expect(multiDestination).NotTo(BeNil(), "Expected a multi-destination for weighted backends")

				// Validate that there are two destinations with the correct weights
				Expect(multiDestination).To(HaveLen(2))

				dest1 := multiDestination[0]
				dest2 := multiDestination[1]

				// Ensure backend names, ports, and weights match expectations
				Expect(dest1.ClusterName).To(Equal("backend-svc1"))
				Expect(dest1.Weight).To(Equal(uint32(60)))

				Expect(dest2.ClusterName).To(Equal("backend-svc2"))
				Expect(dest2.Weight).To(Equal(uint32(40)))
			})
		})

		It("should not create a DestinationSpec when backendRef refers to a service in a different namespace without a permitting ReferenceGrant", func() {
			By("Creating a TCPRoute with a backendRef to a different namespace")
			tcpRoute := tcpRoute("cross-namespace-tcp-route")
			tcpRoute.Spec = gwv1a2.TCPRouteSpec{
				CommonRouteSpec: gwv1.CommonRouteSpec{
					ParentRefs: []gwv1.ParentReference{
						{
							Name:      gwv1.ObjectName("test-gateway"),
							Namespace: ptr.To(gwv1.Namespace("default")),
							Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
						},
					},
				},
				Rules: []gwv1a2.TCPRouteRule{
					{
						BackendRefs: []gwv1.BackendRef{
							{
								BackendObjectReference: gwv1.BackendObjectReference{
									Name:      "backend-svc",
									Namespace: ptr.To(gwv1.Namespace("other-namespace")),
									Port:      ptr.To(gwv1.PortNumber(8080)),
								},
							},
						},
					},
				},
			}

			By("Setting up the mock to return an error when ReferenceGrant is missing")
			tcpIr := tcpToIr(tcpRoute)
			// simulate missing reference grant
			tcpIr.Backends[0].BackendObject = nil
			tcpIr.Backends[0].Err = errors.New("missing reference grant")

			By("Creating the RouteInfo")
			routes := []*query.RouteInfo{
				{
					Object: tcpIr,
				},
			}

			By("Appending the TCP listener")
			ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

			By("Validating that a TCP listener is created with no TCPHosts")
			Expect(ml.Listeners).To(HaveLen(1))

			translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
			Expect(translatedListener).NotTo(BeNil())
			Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

			tcpListener := translatedListener.TcpFilterChain[0]
			Expect(tcpListener).NotTo(BeNil())
			Expect(tcpListener.BackendRefs).To(HaveLen(1))

			tcpHost := tcpListener.BackendRefs[0]
			Expect(tcpHost.BackendObject).To(BeNil())
		})

		/* i think this is not needed, as refgrants are resolved a this point
		It("should create a TCP listener when backendRef refers to a service in a different namespace with a permitting ReferenceGrant", func() {
			By("Creating a TCPRoute with a backendRef to a different namespace")
			tcpRoute := tcpRoute("cross-namespace-tcp-route", "default")
			tcpRoute.Spec = gwv1a2.TCPRouteSpec{
				CommonRouteSpec: gwv1.CommonRouteSpec{
					ParentRefs: []gwv1.ParentReference{
						{
							Name:      gwv1.ObjectName("test-gateway"),
							Namespace: ptr.To(gwv1.Namespace("default")),
							Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
						},
					},
				},
				Rules: []gwv1a2.TCPRouteRule{
					{
						BackendRefs: []gwv1.BackendRef{
							{
								BackendObjectReference: gwv1.BackendObjectReference{
									Name:      "backend-svc",
									Namespace: ptr.To(gwv1.Namespace("other-namespace")),
									Port:      ptr.To(gwv1.PortNumber(8080)),
								},
							},
						},
					},
				},
			}

			By("Setting up the mock to return the service when ReferenceGrant allows it")
			queries.EXPECT().
				GetBackendForRef(gomock.Any(), gomock.Any(), gomock.Any()).
				DoAndReturn(func(ctx context.Context, from query.From, ref *gwv1.BackendObjectReference) (client.Object, error) {
					return &corev1.Service{
						ObjectMeta: metav1.ObjectMeta{
							Name:      string(ref.Name),
							Namespace: string(*ref.Namespace),
						},
					}, nil
				}).
				AnyTimes()

			By("Creating the RouteInfo")
			routes := []*query.RouteInfo{
				{
					Object: tcpToIr(tcpRoute),
				},
			}

			By("Appending the TCP listener")
			ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

			By("Validating that a TCP listener is created with TCPHosts")
			Expect(ml.Listeners).To(HaveLen(1))

			translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
			Expect(translatedListener).NotTo(BeNil())
			aggregateListener := translatedListener.GetAggregateListener()
			Expect(aggregateListener).NotTo(BeNil())
			Expect(aggregateListener.TcpListeners).To(HaveLen(1))

			matchedTcpListener := aggregateListener.TcpListeners[0]
			tcpListener := matchedTcpListener.TcpListener
			Expect(tcpListener).NotTo(BeNil())
			Expect(tcpListener.TcpHosts).To(HaveLen(1))

			tcpHost := tcpListener.TcpHosts[0]
			Expect(tcpHost.Destination.GetSingle()).NotTo(BeNil())
			Expect(tcpHost.Destination.GetSingle().GetKube().GetRef().Name).To(Equal("backend-svc"))
			Expect(tcpHost.Destination.GetSingle().GetKube().GetRef().Namespace).To(Equal("other-namespace"))
		})
		*/

		It("should set listener condition when TCPRoute has empty backend references", func() {
			By("Creating a TCPRoute with an empty backend references")
			tcpRoute := tcpRoute("test-empty-backend")
			tcpRoute.Spec = gwv1a2.TCPRouteSpec{
				CommonRouteSpec: gwv1.CommonRouteSpec{
					ParentRefs: []gwv1.ParentReference{
						{
							Name:      gwv1.ObjectName("test-gateway"),
							Namespace: ptr.To(gwv1.Namespace("default")),
							Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
						},
					},
				},
				Rules: []gwv1a2.TCPRouteRule{
					{BackendRefs: []gwv1.BackendRef{}}, // Empty BackendRefs
				},
			}

			By("Creating the RouteInfo")
			routes := []*query.RouteInfo{
				{
					Object: tcpToIr(tcpRoute),
				},
			}

			By("Appending the TCP listener")
			ml.AppendTcpListener(lisToIr(gwListener), routes, listenerReporter)

			By("Validating that a TCP listener is created with no TCPHosts")
			Expect(ml.Listeners).To(HaveLen(1))

			translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
			Expect(translatedListener).NotTo(BeNil())
			Expect(translatedListener.TcpFilterChain).To(BeEmpty(), "Expected no TCP listeners due to empty backend references")

			By("Validating that the listener condition was set")
			// Get the report map to check the status
			rm := reports.NewReportMap()
			testStatusReporter := reports.NewReporter(&rm)
			testGatewayReporter := testStatusReporter.Gateway(gateway)
			testListenerReporter := testGatewayReporter.Listener(&gwListener)

			// Re-create the MergedListeners with the test reporter to capture status
			testMl := &listener.MergedListeners{
				Listeners: []*listener.MergedListener{},
				Queries:   queries,
			}
			testMl.AppendTcpListener(lisToIr(gwListener), routes, testListenerReporter)
			testMl.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, testStatusReporter)

			// Check that the listener has the expected condition
			gatewayReport := rm.Gateway(gateway)
			Expect(gatewayReport).NotTo(BeNil())
			listenerReporter := gatewayReport.ListenerName(string(gwListener.Name))
			Expect(listenerReporter).NotTo(BeNil())

			// Cast to the concrete type to access the Status field
			listenerReport, ok := listenerReporter.(*reports.ListenerReport)
			Expect(ok).To(BeTrue(), "Expected ListenerReport type")

			// Find the Programmed condition
			var programmedCondition *metav1.Condition
			for i, condition := range listenerReport.Status.Conditions {
				if condition.Type == string(gwv1.ListenerConditionProgrammed) {
					programmedCondition = &listenerReport.Status.Conditions[i]
					break
				}
			}

			Expect(programmedCondition).NotTo(BeNil(), "Expected Programmed condition to be set")
			Expect(programmedCondition.Status).To(Equal(metav1.ConditionFalse))
			Expect(programmedCondition.Reason).To(Equal(string(gwv1.ListenerReasonInvalid)))
			Expect(programmedCondition.Message).To(ContainSubstring("TCP/TLS listener has no valid backends or routes"))
		})
	})
	Context("TLS", func() {
		BeforeEach(func() {
			ctx = context.Background()
			ctrl = gomock.NewController(GinkgoT())
			queries = mocks.NewMockGatewayQueries(ctrl)

			hostname := gwv1.Hostname("example.com")
			gwListener = gwv1.Listener{
				Name:     "foo-tls",
				Protocol: gwv1.TLSProtocolType,
				Port:     443,
				Hostname: &hostname,
			}

			gateway = &gwv1.Gateway{
				ObjectMeta: metav1.ObjectMeta{Name: "test-gateway", Namespace: "default"},
			}

			rm := reports.NewReportMap()
			statusReporter = reports.NewReporter(&rm)
			gatewayReporter := statusReporter.Gateway(gateway)
			listenerReporter = gatewayReporter.Listener(&gwListener)
			ml = &listener.MergedListeners{
				Listeners: []*listener.MergedListener{},
				Queries:   queries,
			}
		})

		AfterEach(func() {
			ctrl.Finish()
		})

		Describe("translates gateway API resources to kgateway proxy listeners", func() {
			It("should create a TLS listener with multiple backend references", func() {
				tlsRoute := tlsRoute("test-tls-route")
				tlsRoute.Spec = gwv1a2.TLSRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Hostnames: []gwv1a2.Hostname{"example.com"},
					Rules: []gwv1a2.TLSRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
									Weight: ptr.To(int32(50)),
								},
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc2",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8082)),
									},
									Weight: ptr.To(int32(50)),
								},
							},
						},
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tlsToIr(tlsRoute),
					},
				}

				By("Appending the TLS listener")
				ml.AppendTlsListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that the TLS listener is properly created with multiple backend references")
				Expect(ml.Listeners).To(HaveLen(1))
				Expect(ml.Listeners[0].TcpFilterChains).To(HaveLen(1))

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tlsListener := translatedListener.TcpFilterChain[0]
				Expect(tlsListener.BackendRefs).To(HaveLen(2))
				Expect(tlsListener.FilterChainCommon.Matcher.SniDomains).To(ContainElement("example.com"))
			})

			It("should skip routes with invalid parent references and process valid ones", func() {
				By("Creating a TLSRoute with a backend reference")
				validRoute := tlsRoute("valid-tls-route")
				validRoute.Spec = gwv1a2.TLSRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TLSRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
								},
							},
						},
					},
				}

				By("Creating an invalid TLSRoute with no parent references")
				invalidRoute := tlsRoute("invalid-tls-route")
				invalidRoute.Spec = gwv1a2.TLSRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{}, // No parent reference provided
					},
				}

				By("Creating the RouteInfo with valid and invalid TCPRoutes")
				routes := []*query.RouteInfo{
					{
						Object: tlsToIr(validRoute),
					},
					{
						Object: tlsToIr(invalidRoute),
					},
				}

				By("Appending the TLS listener")
				ml.AppendTlsListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that one single destination TLS listener is created")
				Expect(ml.Listeners).To(HaveLen(1)) // One valid listener

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tlsListener := translatedListener.TcpFilterChain[0]
				Expect(tlsListener).NotTo(BeNil())
				Expect(tlsListener.BackendRefs[0]).NotTo(BeNil())
				Expect(tlsListener.BackendRefs[0].ClusterName).To(Equal("backend-svc1"))
				Expect(tlsListener.FilterChainCommon.Matcher.SniDomains).To(ContainElement("example.com"))
			})

			It("should create a TLS listener with a single weighted backend reference", func() {
				By("Creating a weighted TLSRoute with a single backend reference")
				tlsRoute := tlsRoute("test-tls-route")
				tlsRoute.Spec = gwv1a2.TLSRouteSpec{
					CommonRouteSpec: gwv1.CommonRouteSpec{
						ParentRefs: []gwv1.ParentReference{
							{
								Name:      gwv1.ObjectName("test-gateway"),
								Namespace: ptr.To(gwv1.Namespace("default")),
								Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
							},
						},
					},
					Rules: []gwv1a2.TLSRouteRule{
						{
							BackendRefs: []gwv1.BackendRef{
								{
									BackendObjectReference: gwv1.BackendObjectReference{
										Name:      "backend-svc1",
										Namespace: ptr.To(gwv1.Namespace("default")),
										Port:      ptr.To(gwv1.PortNumber(8081)),
									},
									Weight: ptr.To(int32(100)),
								},
							},
						},
					},
				}

				By("Creating the RouteInfo")
				routes := []*query.RouteInfo{
					{
						Object: tlsToIr(tlsRoute),
					},
				}

				By("Appending the TLS listener")
				ml.AppendTlsListener(lisToIr(gwListener), routes, listenerReporter)

				By("Validating that one TLS listener is created with a single destination")
				Expect(ml.Listeners).To(HaveLen(1))

				translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, nil, statusReporter)
				Expect(translatedListener).NotTo(BeNil())
				Expect(translatedListener.TcpFilterChain).To(HaveLen(1))

				tlsListener := translatedListener.TcpFilterChain[0]
				Expect(tlsListener).NotTo(BeNil())

				// Access the destination field properly
				Expect(tlsListener.BackendRefs).To(HaveLen(1))
				singleDestination := tlsListener.BackendRefs[0]
				Expect(singleDestination).NotTo(BeNil(), "Expected a single-destination")
				Expect(tlsListener.BackendRefs[0].ClusterName).To(Equal("backend-svc1"))
				Expect(tlsListener.FilterChainCommon.Matcher.SniDomains).To(ContainElement("example.com"))
			})
		})
	})

	Context("TLS listener with TCPRoute", func() {
		var tlsSecretRef gwv1.SecretObjectReference

		BeforeEach(func() {
			ctx = context.Background()
			ctrl = gomock.NewController(GinkgoT())
			queries = mocks.NewMockGatewayQueries(ctrl)

			mode := gwv1.TLSModeTerminate
			hostname := gwv1.Hostname("example.com")
			tlsSecretRef = gwv1.SecretObjectReference{
				Name: "test-secret",
			}

			gwListener = gwv1.Listener{
				Name:     "foo-tls-tcproute",
				Protocol: gwv1.TLSProtocolType,
				Port:     8443,
				Hostname: &hostname,
				TLS: &gwv1.ListenerTLSConfig{
					Mode:            &mode,
					CertificateRefs: []gwv1.SecretObjectReference{tlsSecretRef},
				},
			}

			gateway = &gwv1.Gateway{
				ObjectMeta: metav1.ObjectMeta{Name: "test-gateway", Namespace: "default"},
			}

			rm := reports.NewReportMap()
			statusReporter = reports.NewReporter(&rm)
			gatewayReporter := statusReporter.Gateway(gateway)
			listenerReporter = gatewayReporter.Listener(&gwListener)
			ml = &listener.MergedListeners{
				Listeners: []*listener.MergedListener{},
				Queries:   queries,
			}
		})

		AfterEach(func() {
			ctrl.Finish()
		})

		It("should default to no ALPN for terminated TLS TCPRoutes", func() {
			tcpRoute := tcpRoute("tls-terminated-tcproute")
			tcpRoute.Spec = gwv1a2.TCPRouteSpec{
				CommonRouteSpec: gwv1.CommonRouteSpec{
					ParentRefs: []gwv1.ParentReference{
						{
							Name:      gwv1.ObjectName("test-gateway"),
							Namespace: ptr.To(gwv1.Namespace("default")),
							Kind:      ptr.To(gwv1.Kind(wellknown.GatewayKind)),
						},
					},
				},
				Rules: []gwv1a2.TCPRouteRule{
					{
						BackendRefs: []gwv1.BackendRef{
							{
								BackendObjectReference: gwv1.BackendObjectReference{
									Name:      "backend-svc1",
									Namespace: ptr.To(gwv1.Namespace("default")),
									Port:      ptr.To(gwv1.PortNumber(3001)),
								},
							},
						},
					},
				},
			}

			certPEM, keyPEM, err := k8scert.GenerateSelfSignedCertKey("example.com", nil, nil)
			Expect(err).NotTo(HaveOccurred())

			queries.EXPECT().
				GetSecretForRef(
					gomock.Any(),
					gomock.Any(),
					wellknown.GatewayGVK.GroupKind(),
					"default",
					gomock.Eq(tlsSecretRef),
				).
				Return(&ir.Secret{
					ObjectSource: ir.ObjectSource{
						Group:     "",
						Kind:      "Secret",
						Namespace: "default",
						Name:      string(tlsSecretRef.Name),
					},
					Data: map[string][]byte{
						corev1.TLSCertKey:       certPEM,
						corev1.TLSPrivateKeyKey: keyPEM,
					},
				}, nil)

			routes := []*query.RouteInfo{
				{
					Object: tcpToIr(tcpRoute),
				},
			}

			ml.AppendTlsListener(lisToIr(gwListener), routes, listenerReporter)

			translatedListener := ml.Listeners[0].TranslateListener(krt.TestingDummyContext{}, ctx, queries, statusReporter)
			Expect(translatedListener).NotTo(BeNil())
			Expect(translatedListener.TcpFilterChain).To(HaveLen(1))
			tcpListener := translatedListener.TcpFilterChain[0]
			Expect(tcpListener.FilterChainCommon.TLS).NotTo(BeNil())
			Expect(tcpListener.FilterChainCommon.TLS.AlpnProtocols).To(Equal([]string{string(annotations.AllowEmptyAlpnProtocols)}))
		})
	})
})

func tcpRoute(name string) *gwv1a2.TCPRoute {
	return &gwv1a2.TCPRoute{
		TypeMeta: metav1.TypeMeta{
			Kind:       wellknown.TCPRouteKind,
			APIVersion: gwv1a2.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: "default",
		},
	}
}

func tlsRoute(name string) *gwv1a2.TLSRoute {
	return &gwv1a2.TLSRoute{
		TypeMeta: metav1.TypeMeta{
			Kind:       wellknown.TLSRouteKind,
			APIVersion: gwv1a2.GroupVersion.String(),
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: "default",
		},
	}
}
